package com.studykun.client.util;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public class ObservableArrayList<E> extends ArrayList<E> implements ObservableList<E> {
  private static final long serialVersionUID = 6405037431626973286L;
  
  private final ArrayList<ObservableListHandler<E>> observableListHandlers_ =
    new ArrayList<ObservableListHandler<E>>();
  
  public ObservableArrayList(
      final int initialCapacity) {
    super(initialCapacity);
  }

  public ObservableArrayList() {
  }

  public ObservableArrayList(
      final Collection<? extends E> c) {
    super(c);
  }

  /**
   * Returns a shallow copy of this <tt>ObservableArrayList</tt> instance.
   * (The elements themselves are not copied.)
   * 
   * @return a clone of this <tt>ObservableArrayList</tt> instance.
   */
  public ObservableArrayList<E> clone() {
    return new ObservableArrayList<E>(this);
  }

  /**
   * Replaces the element at the specified position in this list with the
   * specified element.
   * 
   * @param index
   *  index of element to replace.
   * @param element
   *  element to be stored at the specified position.
   * @return
   *  the element previously at the specified position.
   * @throws IndexOutOfBoundsException
   *  if index out of range
   *  <tt>(index &lt; 0 || index &gt;= size())</tt>.
   */
  public E set(
      final int index,
      final E element) {
    final E oldElement = super.set(index, element);
    fireListElementReplaced(index, oldElement, element);
    return oldElement;
  }

  /**
   * Appends the specified element to the end of this list.
   * 
   * @param o
   *  element to be appended to this list.
   * @return
   *  <tt>true</tt> (as per the general contract of Collection.add).
   */
  public boolean add(
      final E o) {
    final int index = size();
    final boolean result = super.add(o);
    fireListElementsAdded(index, subList(index, index + 1));
    return result;
  }

  /**
   * Inserts the specified element at the specified position in this list.
   * Shifts the element currently at that position (if any) and any subsequent
   * elements to the right (adds one to their indices).
   * 
   * @param index
   *  index at which the specified element is to be inserted.
   * @param element
   *  element to be inserted.
   * @throws IndexOutOfBoundsException
   *  if index is out of range
   *  <tt>(index &lt; 0 || index &gt; size())</tt>.
   */
  public void add(
      final int index,
      final E element) {
    super.add(index, element);
    fireListElementsAdded(index, subList(index, index + 1));
  }

  /**
   * Removes the element at the specified position in this list. Shifts any
   * subsequent elements to the left (subtracts one from their indices).
   * 
   * @param index
   *  the index of the element to removed.
   * @return
   *  the element that was removed from the list.
   * @throws IndexOutOfBoundsException
   *  if index out of range <tt>(index &lt; 0 || index &gt;= size())</tt>.
   */
  public E remove(
      final int index) {
    final E oldElement = super.remove(index);
    final ArrayList<E> removedList = new ArrayList<E>(1);
    removedList.add(oldElement);
    fireListElementsRemoved(index, removedList);
    return oldElement;
  }

  /**
   * Removes a single instance of the specified element from this list, if it is
   * present (optional operation). More formally, removes an element <tt>e</tt>
   * such that <tt>(o==null ? e==null :
   * o.equals(e))</tt>, if the list contains one or more such elements. Returns
   * <tt>true</tt> if the list contained the specified element (or equivalently,
   * if the list changed as a result of the call).
   * <p>
   * 
   * @param o
   *  element to be removed from this list, if present.
   * @return
   *  <tt>true</tt> if the list contained the specified element.
   */
  public boolean remove(
      final Object o) {
    final int index = indexOf(o);
    if (index == -1) {
      return false;
    }
    
    final E oldElement = super.remove(index);
    final ArrayList<E> removedList = new ArrayList<E>(1);
    removedList.add(oldElement);
    fireListElementsRemoved(index, removedList);
    
    return true;
  }

  /**
   * Removes all of the elements from this list. The list will be empty after
   * this call returns.
   */
  public void clear() {
    final ArrayList<E> removedList = new ArrayList<E>(this);
    super.clear();
    fireListElementsRemoved(0, removedList);
  }

  /**
   * Appends all of the elements in the specified Collection to the end of this
   * list, in the order that they are returned by the specified Collection's
   * Iterator. The behavior of this operation is undefined if the specified
   * Collection is modified while the operation is in progress. (This implies
   * that the behavior of this call is undefined if the specified Collection is
   * this list, and this list is nonempty.)
   * 
   * @param c
   *  the elements to be inserted into this list.
   * @return
   *  <tt>true</tt> if this list changed as a result of the call.
   * @throws NullPointerException
   *  if the specified collection is null.
   */
  public boolean addAll(
      final Collection<? extends E> c) {
    final int index = size();
    boolean result = super.addAll(c);
    if (result) {
      fireListElementsAdded(index, subList(index, size()));
    }
    return result;
  }

  /**
   * Inserts all of the elements in the specified Collection into this list,
   * starting at the specified position. Shifts the element currently at that
   * position (if any) and any subsequent elements to the right (increases their
   * indices). The new elements will appear in the list in the order that they
   * are returned by the specified Collection's iterator.
   * 
   * @param index
   *  index at which to insert first element from the specified collection.
   * @param c
   *  elements to be inserted into this list.
   * @return
   *  <tt>true</tt> if this list changed as a result of the call.
   * @throws IndexOutOfBoundsException
   *  if index out of range <tt>(index &lt; 0 || index &gt; size())</tt>.
   * @throws NullPointerException
   *  if the specified Collection is null.
   */
  public boolean addAll(
      final int index,
      final Collection<? extends E> c) {
    final int oldSize = size();
    boolean result = super.addAll(index, c);
    if (result) {
      final int newSize = size();
      fireListElementsAdded(index, subList(index, index + newSize - oldSize));
    }
    return result;
  }

  /**
   * Removes from this List all of the elements whose index is between
   * fromIndex, inclusive and toIndex, exclusive. Shifts any succeeding elements
   * to the left (reduces their index). This call shortens the list by
   * <tt>(toIndex - fromIndex)</tt> elements. (If <tt>toIndex==fromIndex</tt>,
   * this operation has no effect.)
   * 
   * @param fromIndex
   *  index of first element to be removed.
   * @param toIndex
   *  index after last element to be removed.
   */
  protected void removeRange(
      final int fromIndex,
      final int toIndex) {
    if (fromIndex == toIndex) {
      return;
    }
    
    final ArrayList<E> oldElements = new ArrayList<E>(subList(fromIndex, toIndex));
    super.removeRange(fromIndex, toIndex);
    fireListElementsRemoved(fromIndex, oldElements);
  }

  public void addObservableListHandler(
      final ObservableListHandler<E> handler) {
    observableListHandlers_.add(handler);
  }

  public void removeObservableListHandler(
      final ObservableListHandler<E> handler) {
    observableListHandlers_.remove(handler);
  }

  protected void fireListElementReplaced(
      final int index,
      final E oldElement,
      final E newElement) {
    for (final ObservableListHandler<E> handler : observableListHandlers_) {
      handler.listElementReplaced(this, index, oldElement, newElement);
    }
  }

  protected void fireListElementsAdded(
      final int index,
      final List<E> elements) {
    for (final ObservableListHandler<E> handler : observableListHandlers_) {
      handler.listElementsAdded(this, index, elements);
    }
  }

  protected void fireListElementsRemoved(
      final int index,
      final List<E> elements) {
    for (final ObservableListHandler<E> handler : observableListHandlers_) {
      handler.listElementsRemoved(this, index, elements);
    }
  }

  public List<E> subList(
      final int fromIndex,
      final int toIndex) {
    final ArrayList<E> newSubList = new ArrayList<E>();
    for (int i = fromIndex; i < toIndex; ++i) {
      newSubList.add(get(i));
    }
    return newSubList;
  }
}
